import json
import math
import time

import  mq
from pymodbus.client.sync import ModbusSerialClient
import  queue
import requests

import objDatas as znData
class TAcq:
    def __init__(self):
        self.info = {}
        self.mqClient = None
        self.tdbClient = None
        self.buses ={}

        self.tdb_tabName = "zn20204_"
        self.tdb_changeTabName = "znchange"

        self.mqClient = mq.TMq()
        self.mqClient.events['onConnect'] = self.onMqClientConnect
        self.mqClient.events['onDisConnect'] = self.onMqClientDisConnect
        self.mqClient.events['onMessage'] = self.onMqClientMessage
    def loadByObjects (self , znAcq , znBuses , znDevs , znTasks , znRegs , znDps):
        try:
            znAcq['mqRootTopic'] = znAcq['mqRootTopic'].replace('\/','/')
            self.info = znAcq
            for b in znBuses:
                bus = TBus()
                bus.acq = self
                b['comPort']=b['comPort'].replace('\/','/')
                bus.info = b
                bus.COMPort = b['comPort']
                bus.baudRate = b['baudRate']
                bus.busKey = b['busCode']
                self.buses[bus.busKey] = bus
            for d in znDevs:
                dev = TDev()
                d['devKey'] = d['devKey'].replace('\/','/')
                dev.info = d
                dev.devKey = d['devKey']
                dev.bus = self.buses[d['busCode']]
                dev.bus.devs[dev.devKey] = dev
            for t in znTasks:
                task = TTask()
                t['readTaskKey'] = t['readTaskKey'].replace('\/', '/')
                t['devKey'] = t['devKey'].replace('\/', '/')
                task.info = t
                task.bus = self.buses[t["busCode"]]
                task.dev = task.bus.devs[t['devKey']]
                task.bus.readTaskList.append(task)
                task.bus.readTasks[t['readTaskKey']] = task
            for r in znRegs:
                reg = TReg()
                r['regKey'] = r['regKey'].replace('\/','/')
                r['readTaskKey'] = r['readTaskKey'].replace('\/','/')
                r['devKey'] = r['devKey'].replace('\/','/')
                reg.info = r
                reg.addr = r['addr']
                reg.bus = self.buses[r['busCode']]
                reg.dev = reg.bus.devs[r['devKey']]
                reg.readTask = reg.bus.readTasks[r['readTaskKey']]
                reg.dev.regs[ r['regKey']] = reg
                reg.dev.regList.append(reg)
                reg.readTask.regs[ r['regKey']] = reg
                reg.readTask.regList.append(reg)
            for p in znDps:
                dp = TDp()
                p['dpKey'] = p['dpKey'].replace('\/','/')
                p['regKey'] = p['regKey'].replace('\/','/')
                p['nextRegKey'] = p['nextRegKey'].replace('\/','/')
                p['readTaskKey'] = p['readTaskKey'].replace('\/','/')
                p['devKey'] = p['devKey'].replace('\/','/')
                p['dpDesc'] = p['dpDesc'].replace('\/','/')
                dp.info = p
                dp.bus = self.buses[p['busCode']]
                dp.dev = dp.bus.devs[p['devKey']]
                dp.readTask = dp.bus.readTasks[p['readTaskKey']]
                dp.reg = dp.dev.regs[p['regKey']]
                if p['nextRegKey'] in dp.dev.regs.keys():
                    dp.nextReg = dp.dev.regs[p['nextRegKey']]
                dp.dev.dps[p['dpKey']] = dp
                dp.dev.dpList.append(dp)
                dp.readTask.dps[p['dpKey']] = dp
                dp.readTask.dpList.append(dp)
                dp.reg.dps[p['dpKey']] = dp
                dp.reg.dpList.append(dp)

            print(self.buses)
        except Exception as er:
            print(er)
    def loadByConfig(self):
        acq =  znData.objDatas[0]['acqs'][0]
        buses = znData.objDatas[0]['buses']
        devs =  znData.objDatas[0]['devs']
        tasks = znData.objDatas[0]['tasks']
        regs = znData.objDatas[0]['regs']
        dps = znData.objDatas[0]['dps']
        self.loadByObjects(acq , buses , devs , tasks , regs , dps)
    def start(self):
        try:
            self.mqClient.host = self.info['mqHost']
            self.mqClient.port = self.info['mqPort']
            self.mqClient.uid = self.info['mqUid']
            self.mqClient.pwd = self.info['mqPwd']
            self.mqClient.subscribeTopics = [self.info['mqRootTopic']]
            self.mqClient.connect()
            for bus in self.buses.values():
                bus.star()
        except Exception as er :
            print(er)
    def saveNotifyOnTask(self, task):
        try:
            tdbData = task.getChangeDps_TDB()
            mqData = task.getChangeDps_MQ()
            print('----------------- change --------------------')
            print( tdbData)
            print(mqData)
            print('---------------------------------------------')
            self.write2TDB(tdbData , False)
            topic = self.info['mqRootTopic']+"/change/"+task.info['devCode']
            self.mqClient.sendMsg(topic, mqData)
        except Exception as er :
            print(er)
    def saveNotifyOnBus(self, bus):
        try:
            for dev in bus.devs.values():
                tdbData = dev.getDps_TDB()
                mqData = dev.getDps_MQ()
                print('----------------- dev datapoints --------------------')
                print( tdbData)
                print(mqData)
                print('---------------------------------------------')

                self.write2TDB(tdbData , True)
                topic = self.info['mqRootTopic']+"/dev/"+dev.info['devCode']
                self.mqClient.sendMsg(topic, mqData)
        except Exception as er :
            print(er)
    def write2TDB(self , datapointStr , rpFlag):
        try:
            host = self.info['tdbHost']
            port = self.info['tdbPort']
            token = self.info['tdbPwd']
            db = self.info['tdbDB']
            headers = {
                "Authorization": token,
                'Content-Type': 'application/json',
            }
            influxdb_url = "http://" + host + ":" + str(port) + '/write?db=' + db
            if rpFlag :
               influxdb_url = influxdb_url + '&rp=rePolicy_1h'
            resp = requests.post(url=influxdb_url, headers=headers, data=datapointStr)
            print('save to tdb:', resp)
        except Exception as er:
            print(er)

    def onMqClientConnect(self , client, userdata, flags, rc):
        try:
            print('connect to mq broker.')
        except Exception as er:
            print(er)
    def onMqClientDisConnect(self , client, userdata, rc):
        try:
            print('disconnect from mq broker.')
        except Exception as er:
            print(er)
    def onMqClientMessage(self , client, userdata, topic, payload,  message):
        try:
            print('rec mq message.' , topic , payload)
        except Exception as er:
            print(er)
class TBus:
    def __init__(self):
        self.acq = None
        self.busKey = ""
        self.info = {}
        self.devs = {}
        self.readTasks ={}
        self.readTaskList = []
        self.writeTaskBuf = queue.Queue()


        self.COMPort= ""
        self.baudRate = 0


        self.modbusClient = None
    def start(self):
        try:
            self.modbusClient = ModbusSerialClient(method='rtu', port=self.COMPort, baudrate=self.baudRate)
            self.modbusClient.connect()
            readTaskIndex= -1
            readTaskNum = len( self.readTaskList)
            while 1>0:
                try:
                    task = None
                    if self.writeTaskBuf.empty():
                        readTaskIndex = readTaskIndex + 1
                        if readTaskIndex>= readTaskNum:
                            readTaskIndex = 0
                        task = self.readTaskList[readTaskIndex]

                        funCode = task.info['funCode']
                        addrFrom = task.info['addrFrom']
                        addrTo = task.info['addrTo']
                        addrNum = addrTo - addrFrom + 1
                        unitId = task.info['unitId']
                        if funCode == 1 :
                            coilRegs = self.modbusClient.read_coils(addrFrom, count=addrNum, unit = unitId)
                            task.decode(coilRegs)
                        elif funCode == 2:
                            disInputRegs = self.modbusClient.read_discrete_inputs(addrFrom , count=addrNum , unit = unitId)
                            task.decode(disInputRegs)
                        elif funCode == 3:
                            holdRegs = self.modbusClient.read_holding_registers(addrFrom, count=addrNum, unit = unitId)
                            task.decode(holdRegs)
                        elif funCode == 4 :
                            inputRegs = self.modbusClient.read_input_registers(addrFrom, count=addrNum, unit = unitId)
                            task.decode(inputRegs)
                        self.acq.saveNotifyOnTask( task)
                        if readTaskIndex + 1 >= readTaskNum:
                            self.acq.saveNotifyOnBus(self)
                    else:
                        task = self.writeTaskBuf.get()
                except Exception as eer:
                    print(eer)
            self.modbusClient.close()
            self.modbusClient = None
        except Exception as er:
            print(er)
class TDev:
    def __init__(self):
        self.devKey = ""
        self.bus = None
        self.info = {}
        self.regs ={}
        self.dps={}
        self.regList = []
        self.dpList = []
    def getDps_TDB(self):
        res =""
        try:
            devCode = self.info['devCode']
            tab = self.bus.acq.tdb_tabName+devCode
            tags =",".join([
                "bus="+self.info['busCode'] ,
                "dev="+self.info['devCode'] ,
            ])
            fields =[]
            for dp in self.dpList:
                k = dp.info['v'+str(dp.dpId)]
                v = str(dp.value)
                fields.append(k+"="+v)
            res = tab +"," +tags +" "+",".join(fields)
        except Exception as er:
            print(er)
        return  res
    def getDps_MQ(self):
        res = {
            "flag":"zn2024",
            "reason":"dev",
            "bus":self.info['busCode'] ,
            "dev":self.info['devCode'] ,
        }
        try:
            dpItems={}
            for dp in self.dpList:
                k = "v"+str(dp.info['dpId'])
                v = dp.value
                dpItems[k]= v
            res["datapoints"] = dpItems
        except Exception as er:
            print(er)
        return  json.dump(res)

class TTask:
    def __init__(self):
        self.bus = None
        self.dev = None
        self.info = {}
        self.regs ={}
        self.dps={}
        self.regList = []
        self.dpList = []

    def decode(self , regs):
        try:
            funCode = self.info['funCode']
            addrFrom = self.info['addrFrom']

            if funCode == 1 or funCode == 2 :
                for reg in self.regList:
                    bit = regs.bits[reg.addr - addrFrom]
                    reg.v = bit
                    reg.l = bit
            elif funCode == 3 or funCode == 4:
                for reg in self.regList:
                    val = regs.registers[reg.addr - addrFrom]
                    reg.v  = val
                    bytesValue = int.to_bytes(val , 2 , byteorder='big')
                    reg.h = bytesValue[0]
                    reg.h = bytesValue[1]
            for dp in self.dpList:
                dp.updateValue()

        except Exception as er:
            print(er)

    def getChangeDps_TDB(self):
        res =""
        try:
            tab = self.bus.acq.tdb_changeTabName
            tags =",".join([
                "bus="+self.info['busCode'] ,
                "dev="+self.info['devCode'] ,
                "taskSer="+str(self.info['readTaskType']) ,
                "funCode="+str(self.info['funCode'])
            ])
            items =[]
            for dp in self.dpList:
                if dp.isChanged():
                    fields =["ov="+str(dp.oldValue),"nv="+str(dp.value)]
                    item = tab + "," + tags+" "+",".join(fields)
                    items.append(item)
            res = "\n".join(items)
        except Exception as er:
            print(er)
        return  res
    def getChangeDps_MQ(self):
        res = {
            "flag":"zn2024",
            "reason":"change",
            "bus":self.info['busCode'] ,
            "dev":self.info['devCode'] ,
            "taskSer":self.info['readTaskType'],
            "funCode":self.info['funCode']
        }
        try:
            dpItems={}
            for dp in self.dpList:
                if dp.isChanged():
                    k = "v"+str(dp.info['dpId'])
                    v = dp.value
                    dpItems[k]= v
            res["datapoints"] = dpItems
        except Exception as er:
            print(er)
        return  json.dump(res)
class TReg :
    def __init__(self):
        self.bus = None
        self.dev = None
        self.readTask = None
        self.info = {}
        self.dps = {}
        self.dpList = []
        self.addr = 0
        self.h = 0
        self.l = 0
        self.v = 0
class TDp :
    def __init__(self):
        self.bus = None
        self.dev =None
        self.readTask = None
        self.reg = None
        self.nextReg = None
        self.info = {}

        self.value = 0
        self.oldValue = 0
        self.updateTime = 0
    def updateValue(self):
        try:
            nowTm = time.time()
            valueType = self.info['valueType']
            nv = 0
            if valueType =='V01' or valueType == 'V02':
                bitFrom = self.info['bitFrom']
                bitLen = self.info['bitLen']
                mask = (math.pow(2,bitLen) - 1)<<bitFrom
                v = self.reg.v & mask
                if v>0:
                    nv = 1
            elif valueType == 'V03':
                nv = int.from_bytes( bytearray([self.reg.h , self.reg.l]) , byteorder='big' , signed= True)
            elif valueType == 'V04':
                nv = int.from_bytes( bytearray([self.reg.h , self.reg.l]) , byteorder='big' , signed= False)
            elif valueType == 'V05':
                nv = int.from_bytes(bytearray([self.reg.h, self.reg.l , self.nextReg.h , self.nextReg.l]), byteorder='big', signed=False)
            elif valueType == 'V09':
                nv = int.from_bytes(bytearray([self.reg.h, self.reg.l , self.nextReg.h , self.nextReg.l]), byteorder='big', signed=True)
            elif valueType =='V06':
                nv = self.reg.h
            elif valueType =='V07':
                nv = self.reg.l
            elif valueType =='V08':
                nv = self.reg.v
            self.oldValue = self.value
            self.value = nv
            self.updateTime = nowTm
        except Exception as er:
            print(er)
    def isChanged(self):
        res = False
        try:
            changeType = self.info['saveNotifyType']
            changeThreshold = self.info['saveNotifyThreshold']
            if self.updateTime == 0 :
                res = True
            elif changeType == 'N01' and self.value!=self.oldValue:
                res = True
            elif changeType == 'N02':
                if self.oldValue == 0 :
                    if self.value!=0:
                        res = True
                else:
                    diff = abs(self.value - self.oldValue)
                    diffRate = diff *1.0 / abs(self.oldValue)
                    if diffRate > changeThreshold:
                        res = True
            elif changeType == 'N03':
                    diff = abs(self.value - self.oldValue)
                    if diff > changeThreshold:
                        res = True
        except Exception as er:
            print(er)
        return  res

acq = TAcq()
acq.loadByConfig()
print(acq.buses)